﻿using System;
using System.Collections.Generic;
using System.Linq;
using IInject.Collections;
using IInject.Internal;

namespace IInject.Registration
{
	/// <summary>
	/// 
	/// </summary>
	public class DefaultRegistryProvider : DisposableContainerMember, IRegistryProvider
	{
		#region Fields



		#endregion

		#region Constructors

		public DefaultRegistryProvider(IContainer container) : base(container)
		{
			Registries = new TypedCollection<IRegistry>(container);
		}

		#endregion

		#region Properties

		protected TypedCollection<IRegistry> Registries { get; set; }

		#endregion

		#region Methods

		/// <summary>
		/// Clears this instance.
		/// </summary>
		public virtual void Clear()
		{
			Registries.Clear();
		}

		/// <summary>
		/// Determines whether this instance can resolve the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="key">The key.</param>
		/// <returns>
		///   <c>true</c> if this instance can resolve the specified type; otherwise, <c>false</c>.
		/// </returns>
		public bool CanResolve(Type type, string key)
		{
			return Registries.Any(r => r.Contains(type, key));
		}

		/// <summary>
		/// Resolves the specified type.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="key">The key.</param>
		/// <returns></returns>
		/// <exception cref="ResolutionException"></exception>
		public RegistryContext Resolve(Type type, string key)
		{
			foreach (var item in Registries)
			{
				if (item.Contains(type, key))
				{
					return item.Get(type, key);
				}
			}

			throw new ResolutionException(type, key);
		}

		/// <summary>
		/// Resolves all.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <returns></returns>
		public IEnumerable<RegistryContext> ResolveAll(Type type)
		{
            return ResolveAll(type, null);
		}

		/// <summary>
		/// Resolves all.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="key">The key.</param>
		/// <returns></returns>
        public IEnumerable<RegistryContext> ResolveAll(Type type, string key)
        {
            foreach (var item in Registries)
            {
                if (item.Contains(type, key))
                {
                    return item.GetAll(type, key);
                }
            }

            return Enumerable.Empty<RegistryContext>();
        }

		/// <summary>
		/// Registers the specified from.
		/// </summary>
		/// <param name="from">From.</param>
		/// <param name="context">The context.</param>
		public void Register(Type from, RegistryContext context)
		{
			var extensible = ((IExtensible)Container);
			extensible.OnContextRegistering(context);

			foreach (var item in Registries)
			{
				if (item.Register(from, context))
				{
					break;
				}
			}

			extensible.OnContextRegistered(context);
		}

		/// <summary>
		/// Registers all.
		/// </summary>
		/// <param name="from">From.</param>
		/// <param name="contexts">The contexts.</param>
		public void RegisterAll(Type from, IEnumerable<RegistryContext> contexts)
		{
            RegisterAll(from, null, contexts);
		}

		/// <summary>
		/// Registers all.
		/// </summary>
		/// <param name="from">From.</param>
		/// <param name="key">The key.</param>
		/// <param name="contexts">The contexts.</param>
        public void RegisterAll(Type from, string key, IEnumerable<RegistryContext> contexts)
		{
			var registryContexts = contexts.ToList();
			var extensible = ((IExtensible) Container);

			foreach (var context in registryContexts)
			{
				extensible.OnContextRegistering(context);
			}

			foreach (var item in Registries)
            {
                if (item.Register(from, key, registryContexts))
                {
                    break;
                }
			}

			foreach (var context in registryContexts)
			{
				extensible.OnContextRegistered(context);
			}
        }

		/// <summary>
		/// Gets the registry.
		/// </summary>
		/// <typeparam name="TRegistry">The type of the registry.</typeparam>
		/// <returns></returns>
		public virtual TRegistry Get<TRegistry>() where TRegistry : IRegistry
		{
			return (TRegistry)Registries.Get(typeof(TRegistry));
		}

		/// <summary>
		/// Adds this instance.
		/// </summary>
		/// <typeparam name="TRegistry">The type of the registry.</typeparam>
		public virtual void Add<TRegistry>() where TRegistry : IRegistry, new()
		{
			Add(new TRegistry());
		}

		/// <summary>
		/// Adds the specified registry.
		/// </summary>
		/// <typeparam name="TRegistry">The type of the registry.</typeparam>
		/// <param name="registry">The registry.</param>
		public virtual void Add<TRegistry>(TRegistry registry) where TRegistry : IRegistry
		{
			Registries.Add(typeof(TRegistry), registry);
		}

		/// <summary>
		/// Removes the specified registry.
		/// </summary>
		/// <typeparam name="TRegistry">The type of the registry.</typeparam>
		/// <param name="registry">The registry.</param>
		public virtual void Remove<TRegistry>(TRegistry registry) where TRegistry : IRegistry
		{
			Registries.Remove(typeof(TRegistry));
		}

		/// <summary>
		/// When overridden in a derived class, disposes of this instances' allocated resources.
		/// </summary>
		/// <param name="disposing"></param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				Registries.Dispose();
			}
		}

		#endregion
	}
}
